﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;

namespace DNX.IoCnoC
{
    public interface IResolverFactory
    {
        string GenerateCode();
        IResolver Create();
        IResolverFactory RegisterAll<T>();
        IResolverFactory AddReferencedAssembly(Type type);
        IResolverFactory Build();
        IResolverFactory Register<T>();
    }

    public class ResolverFactory : IResolverFactory
    {
        private readonly IDependenciesMapper dependencies;
        private readonly IResolverBuilder builder;
        private readonly IDecoratorObjectBuilderFactory decoratorBuilder;
        private readonly IDictionary<string, Type> initializedObjects = new Dictionary<string, Type>();
        private readonly IList<string> delegateSigletonObjects = new List<string>();
        private readonly IDictionary<string, DecoratorObjectInfo> decoratos = new Dictionary<string, DecoratorObjectInfo>();
        private readonly IList<string> initializedDecoratorObjects = new List<string>();
        private readonly IList<Type> rootObjects = new List<Type>();
        private readonly IList<Type> registeredObjects = new List<Type>();
        private readonly IList<Type> behaviors = new List<Type>();
        private readonly IList<string> delegateTransientObjects = new List<string>();

        public ResolverFactory(IDependenciesMapper dependencies,
            IResolverBuilder builder,
            IDecoratorObjectBuilderFactory decoratorBuilder)
        {
            this.dependencies = dependencies;
            this.builder = builder;
            this.decoratorBuilder = decoratorBuilder;
        }

        public string GenerateCode()
        {
            return builder.GenerateCode();
        }

        public IResolver Create()
        {
            return (IResolver)builder.Compile(decoratos.Values.Select(i => i.Unit).ToArray());
        }

        public IResolverFactory RegisterAll<T>()
        {
            var type = typeof(T);
            if (!rootObjects.Contains(type))
                rootObjects.Add(type);
            return this;
        }

        public IResolverFactory AddReferencedAssembly(Type type)
        {
            builder.AddReferencedAssembly(type);
            return this;
        }

        public IResolverFactory Build()
        {
            builder.AddConstructor();
            CreateFactories();
            return this;
        }

        public IResolverFactory Register<T>()
        {
            var type = typeof(T);
            if (!registeredObjects.Contains(type))
                registeredObjects.Add(type);
            return this;
        }

        private void CreateFactories()
        {
            foreach (var root in rootObjects)
            {
                foreach (var item in dependencies.Resolve(root).Twins)
                {
                    if (item.Type == root || !root.IsAssignableFrom(item.Type))
                        continue;
                    CreateInstance(item);
                }
                foreach (var item in initializedObjects)
                {
                    if (initializedDecoratorObjects.Contains(item.Key))
                        continue;
                    initializedDecoratorObjects.Add(item.Key);
                    builder.AddDelegateReference(item.Value.FullName, GetName(item.Value));
                    builder.AddSingheltonFactory(GetName(item.Value), item.Value, item.Value,
                        new ReferenceField { Name = FullName(item.Value) });
                }
            }

            foreach (var root in registeredObjects)
            {
                foreach (var item in dependencies.Resolve(root).Twins)
                {
                    if (!root.IsAssignableFrom(item.Type))
                        continue;
                    CreateInstance(item);
                }
                foreach (var item in initializedObjects)
                {
                    if (initializedDecoratorObjects.Contains(item.Key))
                        continue;
                    initializedDecoratorObjects.Add(item.Key);
                    builder.AddDelegateReference(item.Value.FullName, GetName(item.Value));
                    builder.AddSingheltonFactory(GetName(item.Value), item.Value, item.Value,
                        new ReferenceField { Name = FullName(item.Value) });
                }
            }
        }

        private static bool NotHasSet(PropertyInfo propertyInfo)
        {
            return propertyInfo.GetSetMethod() == null;
        }

        private static bool IsExcludedProperty(PropertyInfo propertyInfo)
        {
            return propertyInfo.GetCustomAttribute<ResolvePropertyAttribute>() == null;
        }

        private ArraySourceParameter InitializePropertyArray(Type propertyType)
        {
            var elementType = propertyType.GetElementType();
            var arraySourceParameter = new ArraySourceParameter
            {
                Type = propertyType,
                SourceParameters = dependencies.Resolve(elementType).Twins.Where(i => elementType.IsAssignableFrom(i.DefaultImplementation)).Select(CreateInstance).ToArray()
            };
            return arraySourceParameter;
        }

        private ISourceParameter[] SourceParameters(ITypeHandle item)
        {
            var sourceParameters = new List<ISourceParameter>();
            if (item.Default != null)
                foreach (var dependency in item.Default.Parameters)
                {
                    if (dependency.DefaultValue != null)
                    {
                        sourceParameters.Add(new ValueSourceParameter
                        {
                            Value = dependency.DefaultValue
                        });
                        continue;
                    }
                    AddSourceParameter(sourceParameters, dependency);
                }
            return sourceParameters.ToArray();
        }

        private void AddSourceParameter(List<ISourceParameter> sourceParameters, IParameterHandle dependency)
        {
            if (dependency.Contract.IsArray)
            {
                var sourceParameter = new ArraySourceParameter
                {
                    Type = dependency.Contract,
                };
                sourceParameters.Add(sourceParameter);
                var contract = dependency.Contract.GetElementType();
                var implementations = dependencies.Resolve(contract).Twins;
                sourceParameter.SourceParameters =
                    implementations.Select(CreateInstance).ToArray();
                return;
            }
            if (dependency.DefaultImplementation != null)
                sourceParameters.Add(CreateInstance(dependencies.Resolve(dependency.DefaultImplementation)));
        }

        private ISourceParameter CreateInstance(ITypeHandle typeHandle)
        {
            ISourceParameter result = null;
            var factoryObjectFullName = FullName(typeHandle.Type);
            if (!delegateSigletonObjects.Contains(factoryObjectFullName))
            {
                delegateSigletonObjects.Add(factoryObjectFullName);
                DefineFactoryFor(typeHandle.Type);
                
            }
            typeHandle.LifeStyle.Transient(() =>
            {
                result = CreateTransient(typeHandle.Type);
                if (delegateTransientObjects.Contains(factoryObjectFullName)) 
                    return;
                delegateTransientObjects.Add(factoryObjectFullName);
                builder.AddDelegateReference(typeHandle.Type.FullName, FactoryName(typeHandle.Type));
            });
            typeHandle.LifeStyle.Singleton(() =>
            {
                var type = typeHandle.Type;
                var fullName = FullName(type);
                if (ObjectIsInitializedSingleton(fullName))
                {
                    MarkObjectAsInitialized(fullName, type);
                    DefineFieldForSingletonObject(fullName, type);
                }
                result = new FieldSourceParameter { Name = fullName };
            });
            return result;
        }

        private bool ObjectIsInitializedSingleton(string type)
        {
            return !initializedObjects.ContainsKey(type);
        }

        private void DefineFieldForProperties(Type type, IMethodStatement methodStatement)
        {
            var properties = type.GetProperties();
            if (!properties.Any())
                return;
            foreach (var propertyInfo in properties)
            {
                if (NotHasSet(propertyInfo))
                    continue;

                if (IsExcludedProperty(propertyInfo))
                    continue;

                var propertyType = propertyInfo.PropertyType;
                var propertyName = propertyInfo.Name;
                if (propertyType.IsArray)
                {
                    methodStatement.AddProperty(propertyName, InitializePropertyArray(propertyType));
                    continue;
                }

                var typeHandle = dependencies.Resolve(propertyType).Twins.FirstOrDefault(i => propertyType.IsAssignableFrom(i.DefaultImplementation));
                if (typeHandle != null)
                    methodStatement.AddProperty(propertyName,
                        CreateInstance(typeHandle));
            }
        }

        private void MarkObjectAsInitialized(string name, Type type)
        {
            if (ObjectIsInitializedSingleton(name))
                initializedObjects.Add(name, type);
        }

        private void DefineFieldForSingletonObject(string fullName, Type type)
        {
            var behavior = type.GetCustomAttributes(typeof(BehaviorAttribute), true).FirstOrDefault();
            if (behavior != null)
            {
                AddDecorator(fullName, type, behavior);
                return;
            }

            builder.AddField(fullName, type);
            builder.AddInitializeField(
                fullName,
                type,
                CreateTransient(type));
        }

        private void AddDecorator(string fullName, Type type, object behavior)
        {
            var behaviorAttributeType = typeof(BehaviorAttribute);
            builder.AddReferencedAssembly(behaviorAttributeType);
            var decorator = decoratorBuilder.AddDecorator(type).GetObjectInfo();
            var name = decorator.FullName();
            if (!decoratos.ContainsKey(name))
                decoratos.Add(name, decorator);

            builder.AddField(fullName, name, decorator.Namespace);
            var typeHandle = dependencies.Resolve(type);
            var lifeStyle = typeHandle.LifeStyle;
            typeHandle.LifeStyle = new TransientAction();
            var behaviorType = behavior.GetType();

            if (!behaviors.Contains(behaviorType))
            {
                behaviors.Add(behaviorType);
                DefineFactoryFor(behaviorType);
            }

            var sourceParameters = new[]
            {
                (ISourceParameter)new FactorySourceNameParameter
                {
                    Name = FactoryName(typeHandle.Type),
                    SourceParameters = new ISourceParameter[0]
                },
                new FactorySourceNameParameter
                {
                    Name = FactoryName(behaviorType),
                    SourceParameters = new ISourceParameter[0]
                }
            };
            var method = builder.AddVariableTransientFactory(
                FactoryName(name),
                name,
                name,
                sourceParameters,
                decorator.Namespace);

            DefineFieldForProperties(typeHandle.Type, method);
            method.AddReturnInstance();
            builder.AddInitializeField(fullName,
                type,
                new FactorySourceNameParameter
                {
                    Name = FactoryName(name),
                    SourceParameters = new ISourceParameter[0]
                });
            typeHandle.LifeStyle = lifeStyle;
            initializedDecoratorObjects.Add(fullName);
            builder.AddSingheltonFactory(fullName + "_Get", name, name, new ReferenceField { Name = fullName }, decorator.Namespace);
            builder.AddDelegateReference(name, fullName + "_Get");
        }

        private void DefineFactoryFor(Type type)
        {
            var behaviorFactory = builder.AddVariableTransientFactory(
                FactoryName(type),
                type,
                type,
                SourceParameters(dependencies.Resolve(type)));
            DefineFieldForProperties(type, behaviorFactory);
            behaviorFactory.AddReturnInstance();
        }

        private ISourceParameter CreateTransient(Type type)
        {
            return new FactorySourceNameParameter
            {
                Name = FactoryName(type),
                SourceParameters = SourceParameters(dependencies.Resolve(type))
            };
        }

        private string FactoryName(string type)
        {
            return string.Format("{0}_Create", FullName(type));
        }

        private string FactoryName(Type type)
        {
            return string.Format("{0}_Create", FullName(type));
        }

        private static string GetName(Type type)
        {
            return string.Format("{0}_Get", FullName(type));
        }
        
        private static string FullName(string fullName)
        {
            return string.Format("_{0}", fullName.GetHashCode().ToString(CultureInfo.InvariantCulture).Replace("-","_"));
        }

        private static string FullName(Type type)
        {
            return FullName(type.FullName);
        }
    }
}